/*
Source Code License Agreement
Copyright (c) 2007, Matthew D. Wright. All rights reserved.

Matthew D. Wright grants to you a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable copyright license, to reproduce,
prepare derivative works of, publicly display, publicly perform, and
distribute this source code and such derivative works in source or
object code form without any attribution requirements.

The name "Matthew D. Wright" must not be used to endorse or promote 
products derived from the source code without prior written permission.

You agree to indemnify, hold harmless and defend Matthew D. Wright from 
and against any loss, damage, claims or lawsuits, including attorney's fees 
that arise or result from your use or distribution of the source code.

THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT
ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ALSO, THERE IS NO WARRANTY OF
NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT. IN NO EVENT SHALL MACROMEDIA
OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package net.nobien.webapis.google.blogger
{
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.HTTPStatusEvent;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.net.URLRequestHeader;
    import flash.net.URLRequestMethod;
    
    import net.nobien.webapis.google.events.BloggerEvent;
    import net.nobien.webapis.google.utils.BloggerHelper;
    
    /**
     * Dispatched when data is received as the download operation progresses.
     * @eventType flash.events.ProgressEvent.PROGRESS
     */
    [Event(name="progress", type="flash.events.ProgressEvent")]
    
    /**
     * Dispatched when a network request returns an HTTP status code.
     * @eventType flash.events.IOErrorEvent.IO_ERROR
     */
    [Event(name="httpStatus", type="flash.events.HTTPStatusEvent")]
    
    /**
     * Dispatched if an error is returned from the Blogger Data API.
     * @eventType flash.events.ErrorEvent.Error
     */
    [Event(name="error", type="flash.events.ErrorEvent")]
    
    /**
     * Dispatched if a call to the server results in a fatal error that 
     * terminates the download.
     * @eventType flash.events.IOErrorEvent.IO_ERROR
     */
    [Event(name="ioError", type="flash.events.IOErrorEvent")]
    
    /**
     * Dispatched when a call attempts to load data from a server outside 
     * the security sandbox.
     * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
     */
    [Event(name="securityError", type="flash.events.SecurityErrorEvent")]
    
    /**
     * Dispatched when a list of blogs is returned after a <code>getBlogs()</code> call 
     * is made.
     * @eventType net.nobien.webapis.google.events.BloggerEvent.BLOG_LIST_RETURNED
     */
    [Event(name="blogListReturned", type="net.nobien.webapis.google.events.BloggerEvent")]
    
    /**
     * Dispatched when a comment is published after a <code>publicComment()</code> call 
     * is made.
     * @eventType net.nobien.webapis.google.events.BloggerEvent.COMMENT_PUBLISHED
     */
    [Event(name="commentPublished", type="net.nobien.webapis.google.events.BloggerEvent")]
    
    /**
     * Dispatched when a comment list is returned after a <code>getComments()</code> call 
     * is made.
     * @eventType net.nobien.webapis.google.events.BloggerEvent.COMMENT_LIST_RETURNED
     */
    [Event(name="commentListReturned", type="net.nobien.webapis.google.events.BloggerEvent")]
    
    /**
     * Dispatched when a comment is deleted d after a <code>deleteComment()</code> call 
     * is made.
     * @eventType net.nobien.webapis.google.events.BloggerEvent.COMMENT_DELETED
     */
    [Event(name="commentDeleted", type="net.nobien.webapis.google.events.BloggerEvent")]
    
    /**
     * Dispatched when a draft is saved after a <code>saveDraft()</code> call is made. 
     * @eventType net.nobien.webapis.google.events.BloggerEvent.DRAFT_SAVED
     */
    [Event(name="draftSaved", type="net.nobien.webapis.google.events.BloggerEvent")]
    
    /**
     * Dispatched when a list of blog posts is returned after a <code>getBlogPosts()</code>
     * call is made.
     * @eventType net.nobien.webapis.google.events.BloggerEvent.POST_LIST_RETURNED
     */
    [Event(name="postListReturned", type="net.nobien.webapis.google.events.BloggerEvent")]
    
    /**
     * Dispatched when a blog post is published after a <code>publishBlogPost()</code>
     * call is made.
     * @eventType net.nobien.webapis.google.events.BloggerEvent.POST_PUBLISHED
     */
    [Event(name="postPublished", type="net.nobien.webapis.google.events.BloggerEvent")]
    
    /**
     * Dispatched when a blog post is updated after an <code>updateBlogPost()</code>
     * call is made.
     * @eventType net.nobien.webapis.google.events.BloggerEvent.POST_UPDATED
     */
    [Event(name="postUpdated", type="net.nobien.webapis.google.events.BloggerEvent")]
    
    /**
     * Dispatched when a blog post is deleted after a <code>deleteBlogPost()</code>
     * call is made.
     * @eventType net.nobien.webapis.google.events.BloggerEvent.POST_DELETED
     */
    [Event(name="postDeleted", type="net.nobien.webapis.google.events.BloggerEvent")]
    
    /**
     * The BloggerService class abstracts the Blogger Data API.
     * More information can be found at 
     * http://code.google.com/apis/blogger/developers_guide_protocol.html
     */
    public class BloggerService extends EventDispatcher
    {
        /**
        * Constant representing the required content type for most requests
        * made to the Blogger Data API.
        */
        public static const CONTENT_TYPE:String = "application/atom+xml";
        
        /**
        * Constant representing the Blogger service name.
        */
        public static const SERVICE_NAME:String = "blogger";
        
        
        private static const HTTP_OK:int = 200;
        private static const HTTP_CREATED:int = 201;
        
        
        private var _authHeader:URLRequestHeader;
        private var _httpStatus:int;
        private var _blogs:Array;
        private var _blogPosts:Array;
        private var _comments:Array;
        private var _savedDraft:BlogPost;
        private var _updatedBlogPost:BlogPost;
        private var _publishedBlogPost:BlogPost;
        private var _publishedComment:Comment;
        
        /**
        * Constructs a new BloggerService object.
        */
        public function BloggerService()
        {
            
        }
        
        /* Generic Event Handlers */
        private function handleLoaderProgress( event:ProgressEvent ):void
        {
            dispatchEvent( event );
        }
        
        private function handleLoaderHTTPStatus( event:HTTPStatusEvent ):void
        {
            _httpStatus = event.status;
            dispatchEvent( event );
        }
        
        private function handleLoaderIOError( event:IOErrorEvent ):void
        {
            dispatchEvent( event );
        }
        
        private function handleLoaderSecurityError( event:SecurityErrorEvent ):void
        {
            dispatchEvent( event );
        }
        
        /**
         * Capture the result of a getBlogs() call.
         * @param event The complete event generated by the URLLoader
         */
        private function handleBlogFeedResponse( event:Event ):void
        {
        	var response:String = URLLoader( event.target ).data as String;
        	
            if( _httpStatus == BloggerService.HTTP_OK )
            {
                var bEvent:BloggerEvent = new BloggerEvent( BloggerEvent.BLOG_LIST_RETURNED );
                bEvent.data = _blogs = BloggerHelper.parseBlogList( response );;
                bEvent.rawRepsone = response;
                
                dispatchEvent( bEvent );
            }
            else
                dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, response ) );
        }
        
        /**
         * Capture the result of a publishBlogPost() call.
         * @param event The complete event generated by the URLLoader
         */
        private function handlePublishBlogPostResponse( event:Event ):void
        {
        	var response:String = URLLoader( event.target ).data as String;
        	
            if( _httpStatus == BloggerService.HTTP_CREATED )
            {
                var bEvent:BloggerEvent = new BloggerEvent( BloggerEvent.POST_PUBLISHED );
                bEvent.data = _publishedBlogPost = BloggerHelper.parseBlogPost( response );
                bEvent.rawRepsone = response;
                
                dispatchEvent( bEvent );
            }
            else
                dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, response ) );
        }
        
        /**
         * Capture the result of a saveDraft() call.
         * @param event The complete event generated by the URLLoader
         */
        private function handleSaveDraftResponse( event:Event ):void
        {
        	var response:String = URLLoader( event.target ).data as String;
            
            if( _httpStatus == BloggerService.HTTP_CREATED )
            {
                var bEvent:BloggerEvent = new BloggerEvent( BloggerEvent.DRAFT_SAVED );
                bEvent.data = _savedDraft = BloggerHelper.parseBlogPost( response );
                bEvent.rawRepsone = response;
                
                dispatchEvent( bEvent );
            }
            else
                dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, response ) );
        }
        
        /**
         * Capture the result of a getBlogPosts() call.
         * @param event The complete event generated by the URLLoader
         */
        private function handlePostListResponse( event:Event ):void
        {
            var response:String = URLLoader( event.target ).data as String;
            
            if( _httpStatus == BloggerService.HTTP_OK )
            {
                var bEvent:BloggerEvent = new BloggerEvent( BloggerEvent.POST_LIST_RETURNED );
                bEvent.data = _blogPosts = BloggerHelper.parseBlogPostList( response );
                bEvent.rawRepsone = response;
                
                dispatchEvent( bEvent );
            }
            else
                dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, response ) );
        }
        
        /**
         * Capture the result of a updateBlogPost() call.
         * @param event The complete event generated by the URLLoader
         */
        private function handlePostUpdateResponse( event:Event ):void
        {
            var response:String = URLLoader( event.target ).data as String;
            
            if( _httpStatus == BloggerService.HTTP_OK )
            {
                var bEvent:BloggerEvent = new BloggerEvent( BloggerEvent.POST_UPDATED );
                bEvent.data = _updatedBlogPost = BloggerHelper.parseBlogPost( response );
                bEvent.rawRepsone = response;
                
                dispatchEvent( bEvent );
            }
            else
                dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, response ) );
        }
        
        /**
         * Capture the result of a deleteBlogPost() call.
         * @param event The complete event generated by the URLLoader
         */
        private function handlePostDeletedResponse( event:Event ):void
        {
            var response:String = URLLoader( event.target ).data as String;
            
        	if( _httpStatus == BloggerService.HTTP_OK )
            {
	            var bEvent:BloggerEvent = new BloggerEvent( BloggerEvent.POST_DELETED );
	            bEvent.rawRepsone = response;
	            
	            dispatchEvent( bEvent );
            }
            else
                dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, response ) );
        }
        
        /**
         * Capture the result of a publishComment() call.
         * @param event The complete event generated by the URLLoader
         */
        private function handleCommentPublishedResponse( event:Event ):void
        {
            var response:String = URLLoader( event.target ).data as String;
            
            if( _httpStatus == BloggerService.HTTP_CREATED )
            {
                var bEvent:BloggerEvent = new BloggerEvent( BloggerEvent.COMMENT_PUBLISHED );
                bEvent.data = _publishedComment = BloggerHelper.parseComment( response );
                bEvent.rawRepsone = response;
                
                dispatchEvent( bEvent );
            }
            else
                dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, response ) );
        }
        
        /**
         * Capture the result of a getComments() call.
         * @param event The complete event generated by the URLLoader
         */
        private function handleCommentListResponse( event:Event ):void
        {
            var response:String = URLLoader( event.target ).data as String;
            
            if( _httpStatus == BloggerService.HTTP_OK )
            {
                var bEvent:BloggerEvent = new BloggerEvent( BloggerEvent.COMMENT_LIST_RETURNED );
                bEvent.data = _comments = BloggerHelper.parseCommentList( response );
                bEvent.rawRepsone = response;
                
                dispatchEvent( bEvent );
            }
            else
                dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, response ) );
        }
        
        /**
         * Capture the result of a deleteComment() call.
         * @param event The complete event generated by the URLLoader
         */
        private function handleCommentDeletedResponse( event:Event ):void
        {
        	var response:String = URLLoader( event.target ).data as String;
            
        	if( _httpStatus == BloggerService.HTTP_OK )
            {
	            var bEvent:BloggerEvent = new BloggerEvent( BloggerEvent.COMMENT_DELETED );
	            bEvent.rawRepsone = response;
	            
	            dispatchEvent( bEvent );
            }
            else
                dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, response ) );
        }
        
        /* Private Methods */
        private function makeRequest( request:URLRequest, callback:Function ):void
        {
            var loader:URLLoader = new URLLoader();
            loader.addEventListener( ProgressEvent.PROGRESS, handleLoaderProgress );
            loader.addEventListener( HTTPStatusEvent.HTTP_STATUS, handleLoaderHTTPStatus );
            loader.addEventListener( SecurityErrorEvent.SECURITY_ERROR, handleLoaderSecurityError );
            loader.addEventListener( IOErrorEvent.IO_ERROR, handleLoaderIOError );
            loader.addEventListener( Event.COMPLETE, callback );
            loader.load( request );
        }
        
        /**
        * Retrieves a list of blogs for the specified user. If no account
        * is specified, the currently authenticated user is used. The resulting
        * data will be an Array of Blog objects.
        * @param userID A Blogger account username
        * @see http://code.google.com/apis/blogger/developers_guide_protocol.html#GettingBlogs
        */
        public function getBlogs( userID:String = "default" ):void
        {
            var url:String = "http://www.blogger.com/feeds/" + userID + "/blogs";
            var request:URLRequest = new URLRequest( url );
            request.method = URLRequestMethod.GET;
            request.requestHeaders.push( _authHeader );

            makeRequest( request, handleBlogFeedResponse );
        }
        
        /**
        * Retrives a list of blog posts for the specefied blog. Optionally
        * you may specify query parameters to customize the response. The 
        * resulting data will be an Array of BlogPost objects.
        * @param blog The blog to retrieve posts from
        * @param categories An optional Array of category names
        * @param updatedMin An optional Date specifying the oldest updated date
        * @param updatedMax An optional Date specifying the youngest updated date
        * @param publishedMin An optional Date specifying the oldest publish date
        * @param publishedMax An optional Date specifying the youngest publish date
        * @param orderBy An optional String to specify the ordering of the results
        * @param maxResults An optional int specifying the maximum amount of results
        * @param startIndex An optional int specifying the start index
        * @see http://code.google.com/apis/blogger/developers_guide_protocol.html#RetrievingPosts
        */
        public function getBlogPosts( blog:Blog, 
                                      categories:Array = null, 
                                      updatedMin:Date = null, 
                                      updatedMax:Date = null, 
                                      publishedMin:Date = null, 
                                      publishedMax:Date = null, 
                                      orderBy:String = null, 
                                      maxResults:int = -1, 
                                      startIndex:int = -1 ):void
        {
            var url:String = blog.postEndPoint + BloggerHelper.buildQueryString( categories, updatedMin, updatedMax, publishedMin, publishedMax, orderBy, maxResults, startIndex );
            var request:URLRequest = new URLRequest( url );
            request.method = URLRequestMethod.GET;
            request.requestHeaders.push( _authHeader );
            
            makeRequest( request, handlePostListResponse );
        }
        
        /**
        * Publishes a blog post. The resulting data will be a BlogPost object.
        * @param blog The blog which the post should be published to
        * @param blogPost The blog post to publish
        * @see http://code.google.com/apis/blogger/developers_guide_protocol.html#create_public_post
        */
        public function publishBlogPost( blog:Blog, blogPost:BlogPost ):void
        {
            var url:String = blog.postEndPoint;
            var request:URLRequest = new URLRequest( url );
            request.method = URLRequestMethod.POST;
            request.requestHeaders.push( _authHeader );
            request.contentType = CONTENT_TYPE;
            request.data = BloggerHelper.serializeBlogPost( blogPost );
            
            makeRequest( request, handlePublishBlogPostResponse );
        }
        
        /**
        * Updates a blog post. The resulting data will be a BlogPost object.
        * @param blogPost The blog post to update
        * @see http://code.google.com/apis/blogger/developers_guide_protocol.html#UpdatingPosts
        */
        public function updateBlogPost( blogPost:BlogPost ):void
        {
            var url:String = blogPost.editEndPoint;
            var request:URLRequest = new URLRequest( url );
            request.method = "PUT";
            request.requestHeaders.push( _authHeader );
            request.requestHeaders.push( new URLRequestHeader( "X-HTTP-Method-Override", "PUT" ) );
            request.contentType = CONTENT_TYPE;
            request.data = BloggerHelper.serializeBlogPost( blogPost );
            
            makeRequest( request, handlePostUpdateResponse );
        }
        
        /**
        * Deletes a blog post.
        * @param blogPost The blog post to delete
        * @see http://code.google.com/apis/blogger/developers_guide_protocol.html#DeletingPosts
        */
        public function deleteBlogPost( blogPost:BlogPost ):void
        {
            var url:String = blogPost.editEndPoint;
            var request:URLRequest = new URLRequest( url );
            request.method = "DELETE";
            request.requestHeaders.push( _authHeader );
            request.requestHeaders.push( new URLRequestHeader( "X-HTTP-Method-Override", "DELETE" ) );
            request.contentType = CONTENT_TYPE;
            
            makeRequest( request, handlePostDeletedResponse );
        }
        
        /**
        * Saves a draft blog post. The resulting data will be a BlogPost object.
        * @param blog The blog which the post should be saved to
        * @param blogPost The blog post
        * @see http://code.google.com/apis/blogger/developers_guide_protocol.html#create_draft_post
        */
        public function saveDraft( blog:Blog, blogPost:BlogPost ):void
        {
            blogPost.draft = true;
            
            var url:String = blog.postEndPoint;
            var request:URLRequest = new URLRequest( url );
            request.method = URLRequestMethod.POST;
            request.requestHeaders.push( _authHeader );
            request.contentType = CONTENT_TYPE;
            request.data = BloggerHelper.serializeBlogPost( blogPost );
            
            makeRequest( request, handleSaveDraftResponse );
        }
        
        /**
        * Retrieves a list of comments for the specified blog post. The resulting 
        * data will be an Array of Comment objects.
        * @param blogPost The blog post to retrieve the comments for
        * @see http://code.google.com/apis/blogger/developers_guide_protocol.html#RetrievingComments
        */
        public function getComments( blogPost:BlogPost ):void
        {
            var url:String = blogPost.commentFeedURL;
            var request:URLRequest = new URLRequest( url );
            request.method = URLRequestMethod.GET;
            request.requestHeaders.push( _authHeader );
            
            makeRequest( request, handleCommentListResponse );
        }
        
        /**
        * Publishes a comment to the specified blog post. The resulting data will 
        * be a Comment object.
        * @param blogPost The blog post to publish the comment to
        * @param comment The comment to publish
        * @see http://code.google.com/apis/blogger/developers_guide_protocol.html#CreatingComments
        */
        public function publishComment( blogPost:BlogPost, comment:Comment ):void
        {
            var url:String = blogPost.commentPostEndPoint;
            var request:URLRequest = new URLRequest( url );
            request.method = URLRequestMethod.POST;
            request.requestHeaders.push( _authHeader );
            request.contentType = CONTENT_TYPE;
            request.data = BloggerHelper.serializeComment( comment );
            
            makeRequest( request, handleCommentPublishedResponse );
        }
        
        /**
        * Deletes a comment.
        * @param comment The comment to delete
        * @see http://code.google.com/apis/blogger/developers_guide_protocol.html#DeletingComments
        */
        public function deleteComment( comment:Comment ):void
        {
            var url:String = comment.editURL;
            var request:URLRequest = new URLRequest( url );
            request.method = "DELETE";
            request.requestHeaders.push( _authHeader );
            request.requestHeaders.push( new URLRequestHeader( "X-HTTP-Method-Override", "DELETE" ) );
            request.contentType = CONTENT_TYPE;
            
            makeRequest( request, handleCommentDeletedResponse );
        }
        
        /**
        * The current URLRequestHeader being used for authenticating 
        * requests to the Blogger Data API.
        */
        public function get authHeader():URLRequestHeader
        {
            return _authHeader;
        }
        
        public function set authHeader( v:URLRequestHeader ):void
        {
            _authHeader = v;
        }
        
        /**
        * The last list of blogs retrieved from a <code>getBlogs()</code> call.
        */
        public function get blogs():Array
        {
            return _blogs;
        }
        
        /**
        * The last list of blog posts retrieved from a <code>getBlogPosts()</code> call.
        */
        public function get blogPosts():Array
        {
            return _blogPosts;
        }
        
        /**
        * The last list of comments retrieved from a <code>getComments()</code> call.
        */
        public function get comments():Array
        {
            return _comments;
        }
        
        /**
        * The last blog post that was updated from an <code>updateBlogPost()</code> call.
        */
        public function get updatedBlogPost():BlogPost
        {
            return _updatedBlogPost;
        }
        
        /**
        * The last draft that was saved from a <code>saveDraft()</code> call.
        */
        public function get savedDraft():BlogPost
        {
            return _savedDraft;
        }
        
        /**
        * The last blog post that was published from an <code>publishBlogPost()</code> call.
        */
        public function get publishedBlogPost():BlogPost
        {
            return _publishedBlogPost;
        }
        
        /**
        * The last comment that was published from an <code>publishComment()</code> call.
        */
        public function get publishedComment():Comment
        {
            return _publishedComment;
        }
    }
    
}